﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Windows.Controls;
using LukasKopenec.LocalizationSupport;
using Notoric.Commanding.CommandBase;
using Notoric.Model.Elements;
using Notoric.Model.Packaging;
using Notoric.ViewModel.Internal;

namespace Notoric.ViewModel.Elements
{
    /// <summary>
    /// Adapts a page.
    /// </summary>
    public class Page : ViewModelElement, IProjectExplorerItem
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="Page"/> instance.
        /// </summary>
        /// <param name="adaptedPart">
        /// <para>
        /// Type: <see cref="PagePart"/>
        /// </para>
        /// <para>
        /// Adapted page part.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="Score"/>
        /// </para>
        /// <para>
        /// The score element that directly owns this page.
        /// </para>
        /// </param>
        /// <param name="name">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The name of the page.
        /// This may be <see langword="null"/>.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="adaptedPart"/> or <paramref name="owner"/> is <see langword="null"/>.
        /// </exception>
        public Page(PagePart adaptedPart, Score owner, string name)
            : base(adaptedPart, owner, new CommandStack(), new CommandStack())
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            this.name = name;
            var pathElement = ((ScorePart)AdaptedPart.Owner).Score.Pages.First(pe => pe.URI == AdaptedPart.Uri);
            pathElement.PropertyChanged += new PropertyChangedEventHandler(pathElement_PropertyChanged);
            projectExplorerHelper = new ProjectExplorerItemHelper(owner, this);
            projectExplorerHelper.SetIcon(EmbeddedBitmapLoader.Load("Pictures.ProjectExplorer.Page.png"));
            projectExplorerHelper.PropertyChanged += new PropertyChangedEventHandler(projectExplorerHelper_PropertyChanged);
            selectedSections = new ObservableCollection<Section>();

            CreateContextMenu();
        }

        #endregion

        #region ViewModelElement Members

        /// <inheritdoc/>
        public override string ElementNameKey
        {
            get { return elementNameKey; }
        }

        #endregion

        #region IProjectExplorerItem Members

        /// <inheritdoc/>
        public ContextMenu ContextMenu
        {
            get { return projectExplorerHelper.ContextMenu; }
        }

        /// <inheritdoc/>
        public string DisplayName
        {
            get 
            {
                if (Name == null)
                    return Number.ToString();
                else
                    return String.Format("{0} [{1}]", Number, Name);
            }
        }

        /// <inheritdoc/>
        public System.Windows.Media.ImageSource Icon
        {
            get { return projectExplorerHelper.Icon; }
        }

        /// <inheritdoc/>
        public bool IsExpanded
        {
            get
            {
                return projectExplorerHelper.IsExpanded;
            }
            set
            {
                projectExplorerHelper.IsExpanded = value;
            }
        }

        /// <inheritdoc/>
        public bool IsInEditMode
        {
            get { return projectExplorerHelper.IsInEditMode; }
            set { projectExplorerHelper.IsInEditMode = value; }
        }

        /// <inheritdoc/>
        public bool IsSelected
        {
            get
            {
                return projectExplorerHelper.IsSelected;
            }
            set
            {
                projectExplorerHelper.IsSelected = value;
            }
        }

        /// <inheritdoc/>
        public string Name
        {
            get { return name; }
            set
            {
                var project = FindOwner<Project>();
                var cmd = new RenamePartCommand(project.UndoStack, project.RedoStack)
                {
                    PathCollection = ((ScorePart)AdaptedPart.Owner).Score.Pages,
                    RenamedPart = this,
                    VirtualPath = value
                };
                cmd.Execute(null);

                RaisePropertyChanged("Name");
                RaisePropertyChanged("DisplayName");
            }
        }
        
        /// <inheritdoc/>
        public IProjectExplorerItem Parent
        {
            get { return projectExplorerHelper.Parent; }
            set { projectExplorerHelper.Parent = value; }
        }

        /// <inheritdoc/>
        public ObservableCollection<IProjectExplorerItem> Children
        {
            get { return projectExplorerHelper.Children; }
        }

        #endregion

        #region Event Handlers
        
        /// <summary>
        /// Occurs when the <see cref="Model.Elements.PageElement.Sections"/> collection of the <see cref="ViewModelElement.AdaptedElement"/> has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void modelSections_CollectionChanged(object sender, Model.Collections.NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case Model.Collections.NotifyCollectionChangedAction.Add:
                    foreach (Model.Elements.SectionElement section in e.NewItems)
                    {
                        var vmSection = CreateViewModelSection(section);
                        vmSection.PropertyChanged += new PropertyChangedEventHandler(vmSection_PropertyChanged);
                        sections.Add(vmSection);
                    }
                    break;
                case Model.Collections.NotifyCollectionChangedAction.Remove:
                    foreach (Model.Elements.SectionElement section in e.OldItems)
                    {
                        var removed = sections.First(s => s.AdaptedElement == section);
                        removed.PropertyChanged -= vmSection_PropertyChanged;
                        sections.Remove(removed);
                    }
                    break;
                case Model.Collections.NotifyCollectionChangedAction.Replace:
                    foreach (Model.Elements.SectionElement section in e.OldItems)
                    {
                        var removed = sections.First(s => s.AdaptedElement == section);
                        removed.PropertyChanged -= vmSection_PropertyChanged;
                        sections.Remove(removed);
                    }
                    foreach (Model.Elements.SectionElement section in e.NewItems)
                    {
                        var vmSection = CreateViewModelSection(section);
                        vmSection.PropertyChanged += new PropertyChangedEventHandler(vmSection_PropertyChanged);
                        sections.Add(vmSection);
                    }
                    break;
                case Model.Collections.NotifyCollectionChangedAction.Reset:
                    foreach (var section in sections)
                        section.PropertyChanged -= vmSection_PropertyChanged;
                    sections.Clear();
                    break;
            }
        }

        /// <summary>
        /// Ocurs when the virtual path to this page has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void pathElement_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "VirtualPath")
            {
                var path = ((Model.Collections.PathElement)sender).VirtualPath;
                name = path.Substring(path.LastIndexOf('/') + 1);
                RaisePropertyChanged("Name");
                RaisePropertyChanged("DisplayName");
            }
        }

        /// <summary>
        /// Occurs when the value of some property of the <see cref="projectExplorerHelper"/> has changed.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// The handler just resends the event via its own <see cref="ViewModelElement.PropertyChanged"/>.
        /// </remarks>
        /// <param name="sender">The object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void projectExplorerHelper_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            RaisePropertyChanged(e);
        }

        /// <summary>
        /// Handles the PropertyChanged event of the sections on this page.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
        private void vmSection_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "IsContentSelected")
            {
                var section = (Section)sender;
                if (section.IsContentSelected)
                {
                    foreach (var sect in sections)
                    {
                        if (sect != section)
                            sect.ClearSelection();
                    }
                }
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Creates the context menu for the page.
        /// </summary>
        private void CreateContextMenu()
        {
            var menu = new ContextMenu();
            var mitem = new MenuItem();
            mitem.Icon = EmbeddedBitmapLoader.LoadImage("Pictures.ProjectExplorer.Open.png", 16, 16);
            mitem.Header = (string)LocalizationManager.GetValue("ProjectExplorer.CommandTitles.Open");
            mitem.Command = ProjectExplorerCommands.OpenCommand;
            mitem.CommandParameter = this;
            menu.Items.Add(mitem);

            mitem = new MenuItem();
            mitem.Header = (string)LocalizationManager.GetValue("ProjectExplorer.CommandTitles.Rename");
            mitem.Command = ProjectExplorerCommands.RenameCommand;
            mitem.CommandParameter = this;
            menu.Items.Add(mitem);

            menu.Items.Add(new Separator());

            mitem = new MenuItem();
            mitem.Icon = EmbeddedBitmapLoader.LoadImage("Pictures.ProjectExplorer.Delete.png", 16, 16);
            mitem.Header = (string)LocalizationManager.GetValue("ProjectExplorer.CommandTitles.Delete");
            mitem.Command = ProjectExplorerCommands.DeleteCommand;
            mitem.CommandParameter = this;
            menu.Items.Add(mitem);

            projectExplorerHelper.SetContextMenu(menu);
        }

        /// <summary>
        /// Creates the <see cref="Sections"/> collection and fills it.
        /// </summary>
        private void CreateSections()
        {
            sections = new ObservableCollection<Section>();
            var modelSections = ((Model.Elements.PageElement)AdaptedElement).Sections;

            foreach (Model.Elements.SectionElement section in modelSections)
            {
                var vmSection = CreateViewModelSection(section);
                vmSection.PropertyChanged += new PropertyChangedEventHandler(vmSection_PropertyChanged);
                sections.Add(vmSection);
            }

            modelSections.CollectionChanged += new EventHandler<Model.Collections.NotifyCollectionChangedEventArgs>(modelSections_CollectionChanged);
        }

        /// <summary>
        /// Creates a view-model <see cref="Section"/> that will adapt a given model <see cref="Model.Elements.SectionElement"/>.
        /// </summary>
        /// <param name="section">Section that will be adapted</param>
        /// <returns>Created view-model section</returns>
        private Section CreateViewModelSection(Model.Elements.SectionElement section)
        {
            if (section is Model.Elements.ImageSectionElement)
                return new ImageSection(section as Model.Elements.ImageSectionElement, this);
            else if (section is Model.Elements.MusicSectionElement)
                return new MusicSection(section as Model.Elements.MusicSectionElement, this);
            else
                return new TextSection(section as Model.Elements.TextSectionElement, this);
        }

        /// <summary>
        /// Gets the type of the section content.
        /// </summary>
        /// <param name="section">
        /// <para>
        /// Type: <see cref="Section"/>
        /// </para>
        /// <para>
        /// The section.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="SelectedContentType"/>
        /// </para>
        /// <para>
        /// The type of the section content.
        /// </para>
        /// </returns>
        private SelectedContentType GetSectionContentType(Section section)
        {
            if (section is ImageSection)
                return SelectedContentType.Image;
            else if (section is TextSection)
                return SelectedContentType.Text;
            else if (section is MusicSection)
                return SelectedContentType.Music;
            else
                return SelectedContentType.None;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Clears the selection in the page.
        /// </summary>
        public void ClearSelection()
        {
            foreach (var section in selectedSections)
                section.IsSelected = false;

            selectedSections.Clear();
            SelectedContent = SelectedContentType.None;
        }

        /// <summary>
        /// Creates a command that sets the background brush of the page.
        /// </summary>
        /// <param name="brush">
        /// <para>
        /// Type: <see cref="System.Windows.Media.Brush"/>
        /// </para>
        /// <para>
        /// The brush that will pain the background of the page.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetBackgroundCommand(System.Windows.Media.Brush brush)
        {
            return new SetterCommand<Brush>(this, "Background", (Brush)BrushConverter.ConvertBack(brush, typeof(Brush), null, CultureInfo.CurrentCulture), UndoStack, RedoStack)
            {
                Name = (string)LocalizationManager.GetValue("Commands.Names.SetBackground")
            };
        }

        /// <summary>
        /// Creates a command that sets the border brush of the page.
        /// </summary>
        /// <param name="brush">
        /// <para>
        /// Type: <see cref="System.Windows.Media.Brush"/>
        /// </para>
        /// <para>
        /// The brush that will pain the border of the page.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetBorderBrushCommand(System.Windows.Media.Brush brush)
        {
            return new SetterCommand<Brush>(this, "BorderBrush", (Brush)BrushConverter.ConvertBack(brush, typeof(Brush), null, CultureInfo.CurrentCulture), UndoStack, RedoStack)
            {
                Name = (string)LocalizationManager.GetValue("Commands.Names.SetBorderBrush")
            };
        }

        /// <summary>
        /// Creates a command that sets the thickness of the page border.
        /// </summary>
        /// <param name="thickness">
        /// <para>
        /// Type: <see cref="System.Windows.Thickness"/>
        /// </para>
        /// <para>
        /// The new thickness of the page border.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetBorderThicknessCommand(System.Windows.Thickness thickness)
        {
            return new SetterCommand<Thickness>(this, "BorderThickness", (Thickness)StructuresConverter.ConvertBack(thickness, typeof(Thickness), null, CultureInfo.CurrentCulture), UndoStack, RedoStack)
            {
                Name = (string)LocalizationManager.GetValue("Commands.Names.SetBorderThickness")
            };
        }

        /// <summary>
        /// Creates a command that sets the height of the page to a given value.
        /// </summary>
        /// <param name="height">
        /// <para>
        /// Type: <see cref="double"/>
        /// </para>
        /// <para>
        /// The new height of the page.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetHeightCommand(double height)
        {
            return new SetterCommand<double>(this, "Height", (double)LengthConverter.ConvertBack(height, typeof(double), null, CultureInfo.CurrentCulture), UndoStack, RedoStack)
            {
                Name = (string)LocalizationManager.GetValue("Commands.Names.SetSize")
            };
        }

        /// <summary>
        /// Creates a command that sets the margin of the page.
        /// </summary>
        /// <param name="margin">
        /// <para>
        /// Type: <see cref="System.Windows.Thickness"/>
        /// </para>
        /// <para>
        /// The new margin of the page.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetMarginCommand(System.Windows.Thickness margin)
        {
            return new SetterCommand<Thickness>(this, "Margin", (Thickness)StructuresConverter.ConvertBack(margin, typeof(Thickness), null, CultureInfo.CurrentCulture), UndoStack, RedoStack)
            {
                Name = (string)LocalizationManager.GetValue("Commands.Names.SetMargin")
            };
        }

        /// <summary>
        /// Creates a command that sets the orientation of the page to a given value.
        /// </summary>
        /// <param name="orientation">
        /// <para>
        /// Type: <see cref="PageOrientation"/>
        /// </para>
        /// <para>
        /// The new orientation of the page.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetOrientationCommand(PageOrientation orientation)
        {
            Model.Elements.PageOrientation val = (orientation == PageOrientation.Portrait ? Model.Elements.PageOrientation.Portrait : Model.Elements.PageOrientation.Landscape);

            var cmd = new SetterCommand<Model.Elements.PageOrientation>(
                this, "Orientation", val, UndoStack, RedoStack
                )
                {
                    Name = (string)LocalizationManager.GetValue("Commands.Names.SetPageOrientation")
                };

            return cmd;
        }

        /// <summary>
        /// Creates a command that sets the size of the page.
        /// </summary>
        /// <param name="width">
        /// <para>
        /// Type: <see cref="double"/>
        /// </para>
        /// <para>
        /// The new width of the page.
        /// </para>
        /// </param>
        /// <param name="height">
        /// <para>
        /// Type: <see cref="double"/>
        /// </para>
        /// <para>
        /// The new height of the page.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetSizeCommand(double width, double height)
        {
            return new SetterCommand<double, double>(UndoStack, RedoStack)
            {
                Target = this,
                Property1 = "Width",
                Property2 = "Height",
                Value1 = (double)LengthConverter.ConvertBack(width, typeof(double), null, CultureInfo.CurrentCulture),
                Value2 = (double)LengthConverter.ConvertBack(height, typeof(double), null, CultureInfo.CurrentCulture),
                Name = (string)LocalizationManager.GetValue("Commands.Names.SetSize")
            };
        }

        /// <summary>
        /// Creates a command that sets the width of the page to a given value.
        /// </summary>
        /// <param name="width">
        /// <para>
        /// Type: <see cref="double"/>
        /// </para>
        /// <para>
        /// The new width of the page.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetWidthCommand(double width)
        {
            return new SetterCommand<double>(this, "Width", (double)LengthConverter.ConvertBack(width, typeof(double), null, CultureInfo.CurrentCulture), UndoStack, RedoStack)
            {
                Name = (string)LocalizationManager.GetValue("Commands.Names.SetSize")
            };
        }

        /// <summary>
        /// Refreshes the selection.
        /// </summary>
        /// <remarks>
        /// This method is a kind of hack. For some reason that I haven't discovered so far,
        /// the selection adorner disappears when I switch from one document content to another.
        /// Setting the <see cref="Section.IsSelected"/> property to <see langword="false"/>
        /// and back to <see langword="true"/> forces the framework to re-render the adorner.
        /// </remarks>
        public void RefreshSelection()
        {
            foreach (var section in selectedSections)
            {
                section.IsSelected = false;
                section.IsSelected = true;
            }
        }

        /// <summary>
        /// Removes a given section from the selection.
        /// </summary>
        /// <param name="section">
        /// <para>
        /// Type: <see cref="Section"/>
        /// </para>
        /// <para>
        /// The removed section.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="section"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="section"/> does not belong to this page.
        /// </exception>
        public void RemoveFromSelection(Section section)
        {
            if (section == null)
                throw new ArgumentNullException("section");
            if (section.FindOwner<Page>() != this)
                throw new ArgumentException("The section does not belong to this page.");

            section.IsSelected = false;
            selectedSections.Remove(section);

            SelectedContentType type = SelectedContentType.None;
            foreach (var sect in selectedSections)
            {
                var sectType = GetSectionContentType(sect);
                if (sectType != type)
                {
                    if (type == SelectedContentType.None)
                        type = sectType;
                    else
                    {
                        type = SelectedContentType.Mixed;
                        break;
                    }
                }
            }
        }

        /// <summary>
        /// Selects a given section in the page.
        /// </summary>
        /// <param name="section">
        /// <para>
        /// Type: <see cref="Section"/>
        /// </para>
        /// <para>
        /// The section to be selected.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="section"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="section"/> does not belong to this page.
        /// </exception>
        public void SelectSection(Section section)
        {
            if (section == null)
                throw new ArgumentNullException("section");
            if (section.FindOwner<Page>() != this)
                throw new ArgumentException("The section does not belong to this page.");

            SelectedContentType sectionType = GetSectionContentType(section);

            if (selectedContent == SelectedContentType.None)
                SelectedContent = sectionType;
            else if (sectionType != selectedContent)
                SelectedContent = SelectedContentType.Mixed;

            section.IsSelected = true;
            selectedSections.Add(section);
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the brush that will paint the background of the page.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.Windows.Media.Brush"/>
        /// </para>
        /// <para>
        /// The brush that will paint the background of the page.
        /// </para>
        /// </value>
        public System.Windows.Media.Brush Background
        {
            get { return (System.Windows.Media.Brush)BrushConverter.Convert(GetPropertyValue<Brush>("Background"), typeof(System.Windows.Media.Brush), null, CultureInfo.CurrentCulture); }
            set 
            {
                if (UnsavedChanges.ContainsKey("Background"))
                {
                    var cmd = (SetterCommand<Brush>)UnsavedChanges["Background"];
                    cmd.Value = (Brush)BrushConverter.ConvertBack(value, typeof(Brush), null, CultureInfo.CurrentCulture);
                }
                else
                    UnsavedChanges["Background"] = CreateSetBackgroundCommand(value);

                RaisePropertyChanged("Background");
            }
        }

        /// <summary>
        /// Gets or sets the brush that will paint the border of the page.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.Windows.Media.Brush"/>
        /// </para>
        /// <para>
        /// The brush that will paing the border of the page.
        /// </para>
        /// </value>
        public System.Windows.Media.Brush BorderBrush
        {
            get { return (System.Windows.Media.Brush)BrushConverter.Convert(GetPropertyValue<Brush>("BorderBrush"), typeof(System.Windows.Media.Brush), null, CultureInfo.CurrentCulture); }
            set 
            {
                if (UnsavedChanges.ContainsKey("BorderBrush"))
                {
                    var cmd = (SetterCommand<Brush>)UnsavedChanges["BorderBrush"];
                    cmd.Value = (Brush)BrushConverter.ConvertBack(value, typeof(Brush), null, CultureInfo.CurrentCulture);
                }
                else
                    UnsavedChanges["BorderBrush"] = CreateSetBorderBrushCommand(value);

                RaisePropertyChanged("BorderBrush");
            }
        }

        /// <summary>
        /// Gets or sets the thickness of the page border.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.Windows.Thickness"/>
        /// </para>
        /// <para>
        /// The thickness (in device independent pixels) of the page border.
        /// </para>
        /// </value>
        public System.Windows.Thickness BorderThickness
        {
            get { return (System.Windows.Thickness)StructuresConverter.Convert(GetPropertyValue<Thickness>("BorderThickness"), typeof(System.Windows.Thickness), null, CultureInfo.CurrentCulture); }
            set 
            {
                if (UnsavedChanges.ContainsKey("BorderThickness"))
                {
                    var cmd = (SetterCommand<Thickness>)UnsavedChanges["BorderThickness"];
                    cmd.Value = (Thickness)StructuresConverter.ConvertBack(value, typeof(Thickness), null, CultureInfo.CurrentCulture);
                }
                else
                    UnsavedChanges["BorderThickness"] = CreateSetBorderThicknessCommand(value);

                RaisePropertyChanged("BorderThickness");
            }
        }

        /// <summary>
        /// Gets or sets the height of the page.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.Double"/>
        /// </para>
        /// <para>
        /// The height (in device independent pixels) of the page.
        /// </para>
        /// </value>
        public double Height
        {
            get { return (double)LengthConverter.Convert(GetPropertyValue<double>("Height"), typeof(double), null, CultureInfo.CurrentCulture); }
            set 
            {
                if (UnsavedChanges.ContainsKey("Height"))
                {
                    var cmd = (SetterCommand<double>)UnsavedChanges["Height"];
                    cmd.Value = (double)LengthConverter.ConvertBack(value, typeof(double), null, CultureInfo.CurrentCulture);
                }
                else
                    UnsavedChanges["Height"] = CreateSetHeightCommand(value);

                RaisePropertyChanged("Height");
            }
        }

        /// <summary>
        /// Gets or sets the margin of the page.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.Windows.Thickness"/>
        /// </para>
        /// <para>
        /// The margin (in device independent pixels) of the page.
        /// </para>
        /// </value>
        public System.Windows.Thickness Margin
        {
            get { return (System.Windows.Thickness)StructuresConverter.Convert(GetPropertyValue<Model.Elements.Thickness>("Margin"), typeof(System.Windows.Thickness), null, CultureInfo.CurrentCulture); }
            set 
            {
                if (UnsavedChanges.ContainsKey("Margin"))
                {
                    var cmd = (SetterCommand<Model.Elements.Thickness>)UnsavedChanges["Margin"];
                    cmd.Value = (Model.Elements.Thickness)StructuresConverter.ConvertBack(value, typeof(Model.Elements.Thickness), null, CultureInfo.CurrentCulture);
                }
                else
                    UnsavedChanges["Margin"] = CreateSetMarginCommand(value);

                RaisePropertyChanged("Margin");
            }
        }

        /// <summary>
        /// Gets or sets the number of the page.
        /// </summary>
        public int Number
        {
            get { return ((Score)Owner).Pages.IndexOf(this) + 1; }
            set { throw new NotImplementedException(); }
        }

        /// <summary>
        /// Gets or sets the orientation of the page.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="PageOrientation"/>
        /// </para>
        /// <para>
        /// The orientation of the page.<br/>
        /// </para>
        /// </value>
        public PageOrientation Orientation
        {
            get 
            {
                Model.Elements.PageOrientation modelOrientation;

                if (UnsavedChanges.ContainsKey("Orientation"))
                {
                    var cmd = (SetterCommand<Model.Elements.PageOrientation>)UnsavedChanges["Orientation"];
                    modelOrientation = cmd.Value;
                }
                else
                    modelOrientation = ((PageElement)AdaptedElement).Orientation;

                if (modelOrientation == Model.Elements.PageOrientation.Portrait)
                    return PageOrientation.Portrait;
                else
                    return PageOrientation.Landscape;
            }
            set
            {
                if (UnsavedChanges.ContainsKey("Orientation"))
                {
                    var cmd = (SetterCommand<Model.Elements.PageOrientation>)UnsavedChanges["Orientation"];
                    cmd.Value = (value == PageOrientation.Portrait ? Model.Elements.PageOrientation.Portrait : Model.Elements.PageOrientation.Landscape);
                }
                else
                    UnsavedChanges["Orientation"] = CreateSetOrientationCommand(value);

                RaisePropertyChanged("Orientation");
            }
        }

        /// <summary>
        /// Gets the collection of section on the page.
        /// </summary>
        /// <remarks>
        /// The sections collection is not created until it is requested for the first time.
        /// This is done to support the delayed loading of the page.
        /// </remarks>
        public ObservableCollection<Section> Sections
        {
            get 
            {
                if (sections == null)
                    CreateSections();
                
                return sections; 
            }
        }

        /// <summary>
        /// Gets the type of the content that is currently selected in the page.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="SelectedContentType"/>
        /// </para>
        /// <para>
        /// The type of the content that is currently selected in the page.
        /// </para>
        /// </value>
        public SelectedContentType SelectedContent
        {
            get { return selectedContent; }
            private set
            {
                selectedContent = value;
                RaisePropertyChanged("SelectedContent");
            }
        }

        /// <summary>
        /// Gets the collection of sections that are currently selected.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ObservableCollection{T}"/> of <see cref="Section"/>
        /// </para>
        /// <para>
        /// The collection of sections that are currently selected.
        /// </para>
        /// </value>
        public ObservableCollection<Section> SelectedSections
        {
            get { return selectedSections; }
        }

        /// <summary>
        /// Gets or sets the width of the page.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.Double"/>
        /// </para>
        /// <para>
        /// The width (in device independent pixels) of the page.
        /// </para>
        /// </value>
        public double Width
        {
            get { return (double)LengthConverter.Convert(GetPropertyValue<double>("Width"), typeof(double), null, CultureInfo.CurrentCulture); }
            set 
            {
                if (UnsavedChanges.ContainsKey("Width"))
                {
                    var cmd = (SetterCommand<double>)UnsavedChanges["Width"];
                    cmd.Value = (double)LengthConverter.ConvertBack(value, typeof(double), null, CultureInfo.CurrentCulture);
                }
                else
                    UnsavedChanges["Width"] = CreateSetWidthCommand(value);

                RaisePropertyChanged("Width");
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds a user-friendly name of this element.
        /// </summary>
        private const string elementNameKey = "ViewModel.ElementNames.Page";
        /// <summary>
        /// Holds the name of this page.
        /// </summary>
        private string name;
        /// <summary>
        /// Holds the project explorer helper implementation.
        /// </summary>
        private ProjectExplorerItemHelper projectExplorerHelper;
        /// <summary>
        /// Holds the collection of sections on this page.
        /// </summary>
        private ObservableCollection<Section> sections;
        /// <summary>
        /// Holds the value of the <see cref="SelectedContent"/> property.
        /// </summary>
        private SelectedContentType selectedContent;
        /// <summary>
        /// Holds the value of the <see cref="SelectedSections"/> property.
        /// </summary>
        private ObservableCollection<Section> selectedSections;

        #endregion
    }

    /// <summary>
    /// Describes the orientation of a <see cref="Page"/>.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The page orientation defines in fact the mapping of the <see cref="Page"/>'s 
    /// <see cref="Page.Width"/> and <see cref="Page.Height"/> properties
    /// to the width and height properties of the view element that presents it.
    /// </para>
    /// <para>
    /// <see cref="PageOrientation.Portrait"/> value defines a direct mapping (Width, Height) = (Width, Height).
    /// Whilst the <see cref="PageOrientation.Landscape"/> defines an inverted mapping (Width, Height) = (Height, Width).
    /// </para>
    /// </remarks>
    public enum PageOrientation
    {
        /// <summary>
        /// Portrait orientation.
        /// </summary>
        Portrait,
        /// <summary>
        /// Landscape orientation.
        /// </summary>
        Landscape
    }

    /// <summary>
    /// Describes the type of the content that is currently selected in the page.
    /// </summary>
    public enum SelectedContentType
    {
        /// <summary>
        /// No content is selected.
        /// </summary>
        None,
        /// <summary>
        /// Musical content is selected.
        /// </summary>
        Music,
        /// <summary>
        /// Image content is selected.
        /// </summary>
        Image,
        /// <summary>
        /// Textual content is selected.
        /// </summary>
        Text,
        /// <summary>
        /// Sections of different types are selected.
        /// </summary>
        Mixed
    }
}
